
#include "BuildinMath.h"


#ifndef UTILS_PERSPECTIVECAMERA
#define UTILS_PERSPECTIVECAMERA

enum class CameraTransform3D {
    Forward, Backword,
    MoveUp, MoveDown, MoveLeft, MoveRight,
    RotateLeft, RotateRight
};

class PerspectiveCamera {
public:
    Matrix4f view = Matrix4f(1.f);
    Matrix4f projection = Matrix4f(1.f);
    Matrix4f view_projection = Matrix4f(1.f);

    Vector3f position = Vector3f(0.0f, 0.0f, 3.0f);
    Vector3f cameraFront = Vector3f(0.0f, 0.0f, -1.0f);
    Vector3f cameraUp = Vector3f(0.0f, 1.0f, 0.0f);
    Vector3f worldUp = Vector3f(0.0f, 1.0f, 0.0f);

    float fov = 45.f;
    float pitch = 0.f;
    float yaw = -90.f;
    float roll = 0.f;
    float aspectRatio = 16.f / 9.f;
    float near = 0.1f;
    float far = 100.f;

    float cameraSpeed = 1.f;
    float cursorSensitivity = 0.2f;
    float scrollSpeed = 0.05f;

    float lastCursorX = 0.f;
    float lastCursorY = 0.f;

    bool isCursorFirstInScreen = true;

    void configure(
        const float _fov,
        const float _aspectRatio,
        const float _near,
        const float _far
    ) {
        fov = _fov;
        aspectRatio = _aspectRatio;
        near = _near;
        far = _far;
        recalculate();
    }

    void setCameraFront(const Vector3f& front) {
        cameraFront = glm::normalize(front);
        recalculate();
    }
    
    void setCameraFront(const float _pitch, const float _yaw, const float _roll) {
        pitch = _pitch;
        yaw = _yaw;
        roll = _roll;
        Vector3f direction;
        direction.x = glm::cos(glm::radians(_pitch)) * glm::cos(glm::radians(_yaw));
        direction.y = glm::sin(glm::radians(_pitch));
        direction.z = glm::cos(glm::radians(_pitch)) * glm::sin(glm::radians(_yaw));
        cameraFront = glm::normalize(direction);
        recalculate();
    }


    void keyboardTransform(const CameraTransform3D direction, const float delta) {
        Vector3f front = glm::normalize(cameraFront);
        Vector2f horizontalDirection_Front = glm::normalize(Vector2f(front[0], front[2]));
        Vector3f right = glm::normalize(glm::cross(worldUp, cameraFront));
        Vector2f horizontalDirection_Right = glm::normalize(Vector2f(right[0], right[2]));

        switch (direction) {
            default: break;
            case CameraTransform3D::Forward:
                position.x += horizontalDirection_Front[0] * delta * cameraSpeed;
                position.z += horizontalDirection_Front[1] * delta * cameraSpeed;
                break;
            case CameraTransform3D::Backword:
                position.x -= horizontalDirection_Front[0] * delta * cameraSpeed;
                position.z -= horizontalDirection_Front[1] * delta * cameraSpeed;
                break;
            case CameraTransform3D::MoveUp:
                position.y += worldUp.y * delta * cameraSpeed;
                break;
            case CameraTransform3D::MoveDown:
                position.y -= worldUp.y * delta * cameraSpeed;
                break;
            case CameraTransform3D::MoveLeft:
                position.x += horizontalDirection_Right[0] * delta * cameraSpeed;
                position.z += horizontalDirection_Right[1] * delta * cameraSpeed;
                break;
            case CameraTransform3D::MoveRight:
                position.x -= horizontalDirection_Right[0] * delta * cameraSpeed;
                position.z -= horizontalDirection_Right[1] * delta * cameraSpeed;
                break;
            case CameraTransform3D::RotateLeft:
                break;
            case CameraTransform3D::RotateRight:
                break;
        }
        recalculate();
    }

    void cursorTransform(const float xpos, const float ypos) {
        float thisMouseX = static_cast<float>(xpos);
        float thisMouseY = static_cast<float>(ypos);
        if (isCursorFirstInScreen) {
            lastCursorX = xpos;
            lastCursorY = ypos;
            isCursorFirstInScreen = false;
        }
        float xOffset = (thisMouseX - lastCursorX) * cursorSensitivity;
        float yOffset = (lastCursorY - thisMouseY) * cursorSensitivity;

        lastCursorX = xpos;
        lastCursorY = ypos;

        pitch += yOffset;
        yaw += xOffset;

        if (pitch > 89.0f)
            pitch = 89.0f;
        if (pitch < -89.0f)
            pitch = -89.0f;

        Vector3f direction;
        direction.x = glm::cos(glm::radians(pitch)) * glm::cos(glm::radians(yaw));
        direction.y = glm::sin(glm::radians(pitch));
        direction.z = glm::cos(glm::radians(pitch)) * glm::sin(glm::radians(yaw));
        cameraFront = glm::normalize(direction);
        Vector3f right = glm::normalize(glm::cross(cameraFront, worldUp));
        cameraUp = glm::normalize(glm::cross(right, cameraFront));
        recalculate();
    }
    void wheelTransform(const float xoffset, const float yoffset) {
        if (fov >= 1.0f && fov <= 45.0f)
            fov -= yoffset * scrollSpeed;
        if (fov <= 1.0f)
            fov = 1.0f;
        if (fov >= 45.0f)
            fov = 45.0f;
        recalculate();
    }

    void recalculate() {
        view = glm::lookAt(position, position + cameraFront, cameraUp);
        projection = glm::perspective(fov, aspectRatio, near, far);
        view_projection = projection * view;
    }
};

#endif // UTILS_PERSPECTIVECAMERA
